import { useEffect, useState, useRef } from "react"
import { useMemoizedFn } from 'ahooks'

export interface HidDeviceInfo {
  vendorId: number
  productId: number
  productName?: string
  connected: boolean
  usage?: number
  usagePage?: number
}

export interface HidDeviceFilter {
  vendorId?: number
  productId?: number
  usage?: number
  usagePage?: number
}

export interface UseHidOptions {
  filters?: HidDeviceFilter[]
  autoConnect?: boolean
  debug?: boolean
  onDeviceConnect?: (info: HidDeviceInfo, device: HIDDevice) => void
  onDeviceDisconnect?: (info: HidDeviceInfo, manual?: boolean) => void
  onDataReceive?: (deviceId: string, data: DataView) => void
  onError?: (error: Error) => void
}

export interface HidDeviceState {
  isConnecting: boolean
  isAuthorizing: boolean
  error: Error | null
  authorizedDevices: HidDeviceInfo[]
  connectedDevices: HidDeviceInfo[]
}

export interface UseHidReturn extends HidDeviceState {
  isSupported: () => boolean
  requestDevice: () => Promise<HIDDevice | null>
  connectDevice: (device: HIDDevice) => Promise<boolean>
  disconnectDevice: (deviceId: string) => Promise<boolean>
  forgetDevice: (deviceId: string) => void
  sendData: (
    deviceId: string,
    data: BufferSource,
    reportId?: number,
    timeout?: number,
    waitForResponse?: boolean
  ) => Promise<DataView | null>
  sendFeatureData: (
    deviceId: string,
    data: BufferSource,
    reportId?: number,
    timeout?: number,
    waitForResponse?: boolean
  ) => Promise<DataView | null>
  reset: () => void
  getDeviceId: (device: HidDeviceInfo | HIDDevice) => string
  getDeviceById: (deviceId: string) => HidDeviceInfo | null
}

const storageKey = 'authorizedHidDevices'

export function useHid(options: UseHidOptions = {}): UseHidReturn {
  const [isConnecting, setIsConnecting] = useState(false);
  const [isAuthorizing, setIsAuthorizing] = useState(false)
  const [error, setError] = useState<Error | null>(null)
  const [authorizedDevices, setAuthorizedDevices] = useState<HidDeviceInfo[]>([])
  const [connectedDevices, setConnectedDevices] = useState<HidDeviceInfo[]>([])

  // 使用 useRef 来存储设备实例，避免重新渲染时丢失
  const deviceInstances = useRef(new Map<string, HIDDevice>())

  const {
    filters = [],
    autoConnect = true,
    debug = false,
    onDeviceConnect,
    onDeviceDisconnect,
    onError,
  } = options

  const log = useMemoizedFn((...args: any[]) => {
    if (debug)
      console.log('[useHid]', ...args)
  })

  const handleError = useMemoizedFn((err: Error) => {
    setError(err)
    onError?.(err)
    log('Error:', err)
  })

  const isSupported = useMemoizedFn((): boolean => {
    return 'hid' in navigator
  })

  const getDeviceId = useMemoizedFn((device: HidDeviceInfo | HIDDevice): string => {
    const vendorId = device?.vendorId ?? 'Unknown vendor ID'
    const productId = device?.productId ?? 'Unknown product ID'
    return `${vendorId}-${productId}`
  })

  const mapDeviceToInfo = useMemoizedFn((device: HIDDevice): HidDeviceInfo => ({
    vendorId: device.vendorId,
    productId: device.productId,
    productName: device.productName || undefined,
    connected: true,
    usage: device.collections[0]?.usage,
    usagePage: device.collections[0]?.usagePage,
  }))

  const saveAuthorizedDevices = useMemoizedFn((devices: HidDeviceInfo[]) => {
    try {
      localStorage.setItem(storageKey, JSON.stringify(devices))
    }
    catch (err) {
      log('Failed to save authorized devices:', err)
    }
  })

  const loadAuthorizedDevices = useMemoizedFn(() => {
    try {
      const saved = localStorage.getItem(storageKey)
      if (saved) {
        const devices = JSON.parse(saved) as HidDeviceInfo[]
        setAuthorizedDevices(devices)
        return devices
      }
    }
    catch (err) {
      log('Failed to load authorized devices:', err)
    }
    return []
  })

  const requestDevice = useMemoizedFn(async (): Promise<HIDDevice | null> => {
    if (!navigator.hid) {
      handleError(new Error('WebHID API is not supported in this browser'))
      return null
    }

    setIsAuthorizing(true)
    setError(null)

    try {
      const devices = await navigator.hid.requestDevice({ filters })
      if (devices.length === 0)
        return null

      const device = devices[0]
      const deviceInfo = mapDeviceToInfo(device)

      setAuthorizedDevices(prev => {
        if (!prev.some(d => getDeviceId(d) === getDeviceId(deviceInfo))) {
          const newDevices = [...prev, deviceInfo]
          saveAuthorizedDevices(newDevices)
          return newDevices
        }
        return prev
      })

      return device
    }
    catch (err: any) {
      if (err.name !== 'NotFoundError') {
        handleError(err)
      }
      return null
    }
    finally {
      setIsAuthorizing(false)
    }
  })

  const connectDevice = useMemoizedFn(async (device: HIDDevice): Promise<boolean> => {
    const deviceId = getDeviceId(device)
    setIsConnecting(true)
    setError(null)

    try {
      await device.open()

      device.addEventListener('inputreport', (event: HIDInputReportEvent) => {
        options.onDataReceive?.(deviceId, event.data)
      })

      deviceInstances.current.set(deviceId, device)
      const deviceInfo = mapDeviceToInfo(device)

      setConnectedDevices(prev => {
        if (!prev.some(d => getDeviceId(d) === deviceId)) {
          const newDevices = [...prev, { ...deviceInfo, connected: true }]
          onDeviceConnect?.(deviceInfo, device)
          return newDevices
        }
        return prev
      })

      return true
    }
    catch (err: any) {
      handleError(err)
      return false
    }
    finally {
      setIsConnecting(false)
    }
  })

  const disconnectDevice = useMemoizedFn(async (deviceId: string): Promise<boolean> => {
    const device = deviceInstances.current.get(deviceId)
    if (!device)
      return false

    try {
      await device.close()
      deviceInstances.current.delete(deviceId)

      setConnectedDevices(prev => {
        const deviceInfo = prev.find(d => getDeviceId(d) === deviceId)
        if (deviceInfo) {
          onDeviceDisconnect?.(deviceInfo, true)
          return prev.filter(d => getDeviceId(d) !== deviceId)
        }
        return prev
      })

      return true
    }
    catch (err: any) {
      handleError(err)
      return false
    }
  })

  const forgetPermission = useMemoizedFn(async (deviceId: string): Promise<boolean> => {
    const device = deviceInstances.current.get(deviceId)
    if (!device)
      return false

    try {
      if ('forget' in HIDDevice.prototype) {
        await device.forget()
        return true
      }
      return false
    }
    catch (err: any) {
      handleError(err)
      return false
    }
  })

  const forgetDevice = useMemoizedFn((deviceId: string) => {
    disconnectDevice(deviceId).finally(async () => {
      await forgetPermission(deviceId)

      setAuthorizedDevices(prev => {
        const newDevices = prev.filter(d => getDeviceId(d) !== deviceId)
        saveAuthorizedDevices(newDevices)
        return newDevices
      })
    })
  })

  const sendDeviceData = useMemoizedFn(async (
    deviceId: string,
    data: BufferSource,
    reportId: number = 0,
    timeout: number = 5000,
    isFeatureReport: boolean = false,
    waitForResponse: boolean = true,
  ): Promise<DataView | null> => {
    const device = deviceInstances.current.get(deviceId)
    if (!device)
      throw new Error('Device not found or not connected')

    try {
      // if not waiting for response, send and return
      if (!waitForResponse) {
        await (isFeatureReport
          ? device.sendFeatureReport(reportId, data)
          : device.sendReport(reportId, data))
        return null
      }

      const receivedDataPromise = new Promise<DataView>((resolve) => {
        const handleInput = (event: HIDInputReportEvent) => {
          device.removeEventListener('inputreport', handleInput)
          resolve(event.data)
        }
        device.addEventListener('inputreport', handleInput)
      })

      const timeoutPromise = new Promise<null>((resolve) => {
        setTimeout(() => resolve(null), timeout)
      })

      // Send data using appropriate method
      await (isFeatureReport
        ? device.sendFeatureReport(reportId, data)
        : device.sendReport(reportId, data))

      return await Promise.race([receivedDataPromise, timeoutPromise])
    }
    catch (err: any) {
      handleError(err)
      throw err
    }
  })

  const sendData = useMemoizedFn(async (
    deviceId: string,
    data: BufferSource,
    reportId: number = 0,
    timeout: number = 5000,
    waitForResponse: boolean = true,
  ): Promise<DataView | null> => {
    return await sendDeviceData(deviceId, data, reportId, timeout, false, waitForResponse)
  })

  const sendFeatureData = useMemoizedFn(async (
    deviceId: string,
    data: BufferSource,
    reportId: number = 0,
    timeout: number = 5000,
    waitForResponse: boolean = true,
  ): Promise<DataView | null> => {
    return await sendDeviceData(deviceId, data, reportId, timeout, true, waitForResponse)
  })

  const reset = useMemoizedFn(() => {
    setError(null)
    setIsConnecting(false)
    setIsAuthorizing(false)
    setConnectedDevices([])
    deviceInstances.current.clear()
  })

  const getDeviceById = useMemoizedFn((deviceId: string): HidDeviceInfo | null => {
    return connectedDevices.find(d => getDeviceId(d) === deviceId) || null
  })

  const handleConnect = useMemoizedFn(async (event: HIDConnectionEvent) => {
    log('Device connected:', event.device)
    if (autoConnect)
      await connectDevice(event.device)
  })

  const handleDisconnect = useMemoizedFn((event: HIDConnectionEvent) => {
    const deviceId = getDeviceId(event.device)
    log('Device disconnected:', deviceId)

    setConnectedDevices(prev => {
      const deviceInfo = prev.find(d => getDeviceId(d) === deviceId)
      if (deviceInfo) {
        deviceInstances.current.delete(deviceId)
        onDeviceDisconnect?.(deviceInfo)
        return prev.filter(d => getDeviceId(d) !== deviceId)
      }
      return prev
    })
  })

  // 生命周期钩子
  useEffect(() => {
    const devices = loadAuthorizedDevices()
    navigator.hid?.addEventListener('connect', handleConnect)
    navigator.hid?.addEventListener('disconnect', handleDisconnect)

    return () => {
      navigator.hid?.removeEventListener('connect', handleConnect)
      navigator.hid?.removeEventListener('disconnect', handleDisconnect)
      // disconnect all devices
      connectedDevices.forEach((device) => {
        disconnectDevice(getDeviceId(device)).catch(log)
      })
    }
  }, []) // 空依赖数组，只在组件挂载时执行一次

  return {
    isConnecting,
    isAuthorizing,
    error,
    authorizedDevices,
    connectedDevices,

    isSupported,
    requestDevice,
    connectDevice,
    disconnectDevice,
    forgetDevice,
    sendData,
    sendFeatureData,
    reset,
    getDeviceId,
    getDeviceById,
  }
}

export default useHid
